Zlepšete Express.js aplikace pomocí typové bezpečnosti TypeScriptu. Pokrývá definice obslužných rutin tras, typování middleware a osvědčené postupy pro škálovatelná API.
Integrace TypeScriptu s Expressem: Typová bezpečnost obslužných rutin tras
TypeScript se stal základním kamenem moderního vývoje v JavaScriptu, nabízí možnosti statického typování, které zlepšují kvalitu kódu, udržovatelnost a škálovatelnost. V kombinaci s Express.js, populárním frameworkem pro webové aplikace v Node.js, může TypeScript významně zvýšit robustnost vašich backend API. Tento komplexní průvodce zkoumá, jak využít TypeScript k dosažení typové bezpečnosti obslužných rutin tras v aplikacích Express.js, a poskytuje praktické příklady a osvědčené postupy pro budování robustních a udržovatelných API pro globální publikum.
Proč je typová bezpečnost důležitá v Express.js
V dynamických jazycích, jako je JavaScript, jsou chyby často zachyceny až za běhu, což může vést k neočekávanému chování a obtížně laditelným problémům. TypeScript to řeší zavedením statického typování, což vám umožňuje zachytit chyby během vývoje, než se dostanou do produkce. V kontextu Express.js je typová bezpečnost obzvláště zásadní pro obslužné rutiny tras, kde pracujete s objekty požadavků a odpovědí, parametry dotazů a těly požadavků. Nesprávné zpracování těchto prvků může vést k pádům aplikace, poškození dat a bezpečnostním zranitelnostem.
- Včasná detekce chyb: Zachyťte chyby související s typy během vývoje, čímž snížíte pravděpodobnost překvapení za běhu.
- Vylepšená udržovatelnost kódu: Typové anotace usnadňují pochopení a refaktorování kódu.
- Vylepšené dokončování kódu a nástroje: IDE mohou poskytovat lepší návrhy a kontrolu chyb s informacemi o typech.
- Snížení počtu chyb: Typová bezpečnost pomáhá předcházet běžným programovacím chybám, jako je předávání nesprávných datových typů funkcím.
Nastavení projektu TypeScript Express.js
Než se pustíme do typové bezpečnosti obslužných rutin tras, nastavme si základní projekt TypeScript Express.js. To poslouží jako základ pro naše příklady.
Předpoklady
- Nainstalovaný Node.js a npm (Node Package Manager). Můžete si je stáhnout z oficiálních webových stránek Node.js. Ujistěte se, že máte aktuální verzi pro optimální kompatibilitu.
- Editor kódu jako Visual Studio Code, který nabízí vynikající podporu TypeScriptu.
Inicializace projektu
- Vytvořte nový adresář projektu:
mkdir typescript-express-app && cd typescript-express-app - Inicializujte nový npm projekt:
npm init -y - Nainstalujte TypeScript a Express.js:
npm install typescript express - Nainstalujte soubory deklarací TypeScriptu pro Express.js (důležité pro typovou bezpečnost):
npm install @types/express @types/node - Inicializujte TypeScript:
npx tsc --init(Tím se vytvoří soubortsconfig.json, který konfiguruje kompilátor TypeScriptu.)
Konfigurace TypeScriptu
Otevřete soubor tsconfig.json a nakonfigurujte jej. Zde je ukázková konfigurace:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
Klíčové konfigurace, které je třeba si všimnout:
target: Určuje cílovou verzi ECMAScriptu.es6je dobrý výchozí bod.module: Určuje generování kódu modulu.commonjsje běžnou volbou pro Node.js.outDir: Určuje výstupní adresář pro zkompilované soubory JavaScriptu.rootDir: Určuje kořenový adresář vašich zdrojových souborů TypeScriptu.strict: Povoluje všechny možnosti striktní kontroly typů pro zvýšenou typovou bezpečnost. Toto je vysoce doporučeno.esModuleInterop: Povoluje interoperabilitu mezi CommonJS a ES moduly.
Vytvoření vstupního bodu
Vytvořte adresář src a přidejte soubor index.ts:
mkdir src
touch src/index.ts
Naplňte src/index.ts základním nastavením serveru Express.js:
import express, { Request, Response } from 'express';
const app = express();
const port = 3000;
app.get('/', (req: Request, res: Response) => {
res.send('Hello, TypeScript Express!');
});
app.listen(port, () => {
console.log(`Server běží na http://localhost:${port}`);
});
Přidání skriptu pro sestavení
Přidejte skript pro sestavení do souboru package.json pro kompilaci kódu TypeScriptu:
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "npm run build && npm run start"
}
Nyní můžete spustit npm run dev k sestavení a spuštění serveru.
Typová bezpečnost obslužných rutin tras: Definování typů požadavků a odpovědí
Jádro typové bezpečnosti obslužných rutin tras spočívá ve správném definování typů pro objekty Request a Response. Express.js poskytuje generické typy pro tyto objekty, které vám umožňují specifikovat typy parametrů dotazů, těl požadavků a parametrů tras.
Základní typy obslužných rutin tras
Začněme s jednoduchou obslužnou rutinou trasy, která očekává jméno jako parametr dotazu:
import express, { Request, Response } from 'express';
const app = express();
const port = 3000;
interface NameQuery {
name: string;
}
app.get('/hello', (req: Request, res: Response) => {
const name = req.query.name;
if (!name) {
return res.status(400).send('Parametr jméno je povinný.');
}
res.send(`Ahoj, ${name}!`);
});
app.listen(port, () => {
console.log(`Server běží na http://localhost:${port}`);
});
V tomto příkladu:
Request<any, any, any, NameQuery>definuje typ pro objekt požadavku.- První
anypředstavuje parametry trasy (např./users/:id). - Druhé
anypředstavuje typ těla odpovědi. - Třetí
anypředstavuje typ těla požadavku. NameQueryje rozhraní, které definuje strukturu parametrů dotazu.
Definováním rozhraní NameQuery může TypeScript nyní ověřit, že vlastnost req.query.name existuje a je typu string. Pokud se pokusíte přistoupit k neexistující vlastnosti nebo přiřadit hodnotu nesprávného typu, TypeScript označí chybu.
Zpracování těl požadavků
Pro trasy, které přijímají těla požadavků (např. POST, PUT, PATCH), můžete definovat rozhraní pro tělo požadavku a použít jej v typu Request:
import express, { Request, Response } from 'express';
import bodyParser from 'body-parser';
const app = express();
const port = 3000;
app.use(bodyParser.json()); // Důležité pro parsování těl požadavků JSON
interface CreateUserRequest {
firstName: string;
lastName: string;
email: string;
}
app.post('/users', (req: Request, res: Response) => {
const { firstName, lastName, email } = req.body;
// Validace těla požadavku
if (!firstName || !lastName || !email) {
return res.status(400).send('Chybí povinná pole.');
}
// Zpracování vytvoření uživatele (např. uložení do databáze)
console.log(`Vytváření uživatele: ${firstName} ${lastName} (${email})`);
res.status(201).send('Uživatel byl úspěšně vytvořen.');
});
app.listen(port, () => {
console.log(`Server běží na http://localhost:${port}`);
});
V tomto příkladu:
CreateUserRequestdefinuje strukturu očekávaného těla požadavku.app.use(bodyParser.json())je zásadní pro parsování těl požadavků JSON. Bez něj byreq.bodybylo nedefinováno.- Typ
Requestje nyníRequest<any, any, CreateUserRequest>, což naznačuje, že tělo požadavku by mělo odpovídat rozhraníCreateUserRequest.
TypeScript nyní zajistí, že objekt req.body obsahuje očekávané vlastnosti (firstName, lastName a email) a že jejich typy jsou správné. To významně snižuje riziko chyb za běhu způsobených nesprávnými daty v těle požadavku.
Zpracování parametrů trasy
Pro trasy s parametry (např. /users/:id) můžete definovat rozhraní pro parametry trasy a použít jej v typu Request:
import express, { Request, Response } from 'express';
const app = express();
const port = 3000;
interface UserParams {
id: string;
}
interface User {
id: string;
firstName: string;
lastName: string;
email: string;
}
const users: User[] = [
{ id: '1', firstName: 'John', lastName: 'Doe', email: 'john.doe@example.com' },
{ id: '2', firstName: 'Jane', lastName: 'Smith', email: 'jane.smith@example.com' },
];
app.get('/users/:id', (req: Request, res: Response) => {
const userId = req.params.id;
const user = users.find(u => u.id === userId);
if (!user) {
return res.status(404).send('Uživatel nenalezen.');
}
res.json(user);
});
app.listen(port, () => {
console.log(`Server běží na http://localhost:${port}`);
});
V tomto příkladu:
UserParamsdefinuje strukturu parametrů trasy, specifikuje, že parametridby měl být řetězec.- Typ
Requestje nyníRequest<UserParams>, což naznačuje, že objektreq.paramsby měl odpovídat rozhraníUserParams.
TypeScript nyní zajistí, že vlastnost req.params.id existuje a je typu string. To pomáhá předcházet chybám způsobeným přístupem k neexistujícím parametrům trasy nebo jejich použitím s nesprávnými typy.
Specifikace typů odpovědí
Zatímco zaměření na typovou bezpečnost požadavků je klíčové, definování typů odpovědí také zlepšuje přehlednost kódu a pomáhá předcházet nekonzistencím. Můžete definovat typ dat, která odesíláte zpět v odpovědi.
import express, { Request, Response } from 'express';
const app = express();
const port = 3000;
interface User {
id: string;
firstName: string;
lastName: string;
email: string;
}
const users: User[] = [
{ id: '1', firstName: 'John', lastName: 'Doe', email: 'john.doe@example.com' },
{ id: '2', firstName: 'Jane', lastName: 'Smith', email: 'jane.smith@example.com' },
];
app.get('/users', (req: Request, res: Response) => {
res.json(users);
});
app.listen(port, () => {
console.log(`Server běží na http://localhost:${port}`);
});
Zde Response<User[]> specifikuje, že tělo odpovědi by mělo být pole objektů User. To pomáhá zajistit, že ve svých odpovědích API důsledně odesíláte správnou datovou strukturu. Pokud se pokusíte odeslat data, která neodpovídají typu `User[]`, TypeScript vydá varování.
Typová bezpečnost middleware
Middleware funkce jsou zásadní pro řešení průřezových záležitostí v aplikacích Express.js. Zajištění typové bezpečnosti v middleware je stejně důležité jako u obslužných rutin tras.
Typování middleware funkcí
Základní struktura middleware funkce v TypeScriptu je podobná struktuře obslužné rutiny trasy:
import express, { Request, Response, NextFunction } from 'express';
function authenticationMiddleware(req: Request, res: Response, next: NextFunction) {
// Autentizační logika
const isAuthenticated = true; // Nahraďte skutečnou kontrolou autentizace
if (isAuthenticated) {
next(); // Pokračujte k dalšímu middleware nebo obslužné rutině trasy
} else {
res.status(401).send('Neautorizováno');
}
}
const app = express();
const port = 3000;
app.use(authenticationMiddleware);
app.get('/', (req: Request, res: Response) => {
res.send('Ahoj, autentizovaný uživateli!');
});
app.listen(port, () => {
console.log(`Server běží na http://localhost:${port}`);
});
V tomto příkladu:
NextFunctionje typ poskytovaný Express.js, který představuje další middleware funkci v řetězci.- Funkce middleware přijímá stejné objekty
RequestaResponsejako obslužné rutiny tras.
Rozšíření objektu požadavku (Request Object)
Někdy můžete chtít přidat vlastní vlastnosti k objektu Request ve svém middleware. Například autentizační middleware může přidat vlastnost user k objektu požadavku. Abyste toho dosáhli typově bezpečným způsobem, musíte rozšířit rozhraní Request.
import express, { Request, Response, NextFunction } from 'express';
interface User {
id: string;
username: string;
email: string;
}
// Rozšíření rozhraní Request
declare global {
namespace Express {
interface Request {
user?: User;
}
}
}
function authenticationMiddleware(req: Request, res: Response, next: NextFunction) {
// Autentizační logika (nahraďte skutečnou kontrolou autentizace)
const user: User = { id: '123', username: 'johndoe', email: 'john.doe@example.com' };
req.user = user; // Přidání uživatele k objektu požadavku
next(); // Pokračujte k dalšímu middleware nebo obslužné rutině trasy
}
const app = express();
const port = 3000;
app.use(authenticationMiddleware);
app.get('/', (req: Request, res: Response) => {
const username = req.user?.username || 'Host';
res.send(`Ahoj, ${username}!`);
});
app.listen(port, () => {
console.log(`Server běží na http://localhost:${port}`);
});
V tomto příkladu:
- Používáme globální deklaraci pro rozšíření rozhraní
Express.Request. - Přidáváme volitelnou vlastnost
usertypuUserdo rozhraníRequest. - Nyní můžete přistupovat k vlastnosti
req.userve svých obslužných rutinách tras, aniž by si TypeScript stěžoval. Znak `?` v `req.user?.username` je klíčový pro zpracování případů, kdy uživatel není autentizován, čímž se předchází potenciálním chybám.
Osvědčené postupy pro integraci TypeScriptu s Expressem
Pro maximalizaci výhod TypeScriptu ve vašich aplikacích Express.js dodržujte tyto osvědčené postupy:
- Povolte striktní režim: Použijte možnost
"strict": trueve svém souborutsconfig.jsonk povolení všech striktních možností kontroly typů. To pomáhá včas zachytit potenciální chyby a zajišťuje vyšší úroveň typové bezpečnosti. - Používejte rozhraní a aliasy typů: Definujte rozhraní a aliasy typů pro reprezentaci struktury vašich dat. To činí váš kód čitelnějším a udržovatelnějším.
- Používejte generické typy: Využijte generické typy k vytváření opakovaně použitelných a typově bezpečných komponent.
- Pište jednotkové testy: Pište jednotkové testy k ověření správnosti vašeho kódu a zajištění přesnosti vašich typových anotací. Testování je klíčové pro udržení kvality kódu.
- Používejte Linter a Formatter: Používejte linter (jako ESLint) a formatter (jako Prettier) k prosazení konzistentních stylů kódování a zachycení potenciálních chyb.
- Vyhněte se
anytypu: Minimalizujte použití typuany, protože obchází kontrolu typů a poráží smysl používání TypeScriptu. Používejte jej pouze tehdy, když je to absolutně nezbytné, a zvažte použití specifičtějších typů nebo generik, kdykoli je to možné. - Logicky strukturojte svůj projekt: Uspořádejte svůj projekt do modulů nebo složek na základě funkcionality. To zlepší udržovatelnost a škálovatelnost vaší aplikace.
- Používejte injektáž závislostí: Zvažte použití kontejneru pro injektáž závislostí pro správu závislostí vaší aplikace. To může učinit váš kód lépe testovatelným a udržovatelným. Knihovny jako InversifyJS jsou populární volbou.
Pokročilé koncepty TypeScriptu pro Express.js
Používání dekorátorů
Dekorátory poskytují stručný a expresivní způsob přidávání metadat ke třídám a funkcím. Dekorátory můžete použít ke zjednodušení registrace tras v Express.js.
Nejprve je třeba povolit experimentální dekorátory ve vašem souboru tsconfig.json přidáním "experimentalDecorators": true do compilerOptions.
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true
}
}
Poté můžete vytvořit vlastní dekorátor pro registraci tras:
import express, { Router, Request, Response } from 'express';
function route(method: string, path: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
if (!target.__router__) {
target.__router__ = Router();
}
target.__router__[method](path, descriptor.value);
};
}
class UserController {
@route('get', '/users')
getUsers(req: Request, res: Response) {
res.send('Seznam uživatelů');
}
@route('post', '/users')
createUser(req: Request, res: Response) {
res.status(201).send('Uživatel vytvořen');
}
public getRouter() {
return this.__router__;
}
}
const userController = new UserController();
const app = express();
const port = 3000;
app.use('/', userController.getRouter());
app.listen(port, () => {
console.log(`Server běží na http://localhost:${port}`);
});
V tomto příkladu:
- Dekorátor
routepřijímá jako argumenty HTTP metodu a cestu. - Registruje dekorovanou metodu jako obslužnou rutinu trasy na routeru spojeném se třídou.
- To zjednodušuje registraci tras a činí váš kód čitelnějším.
Používání vlastních typových strážců
Typové strážce jsou funkce, které zužují typ proměnné v rámci specifického rozsahu. Vlastní typové strážce můžete použít k validaci těl požadavků nebo parametrů dotazů.
interface Product {
id: string;
name: string;
price: number;
}
function isProduct(obj: any): obj is Product {
return typeof obj === 'object' &&
obj !== null &&
typeof obj.id === 'string' &&
typeof obj.name === 'string' &&
typeof obj.price === 'number';
}
import express, { Request, Response } from 'express';
import bodyParser from 'body-parser';
const app = express();
const port = 3000;
app.use(bodyParser.json());
app.post('/products', (req: Request, res: Response) => {
if (!isProduct(req.body)) {
return res.status(400).send('Neplatná data produktu');
}
const product: Product = req.body;
console.log(`Vytváření produktu: ${product.name}`);
res.status(201).send('Produkt vytvořen');
});
app.listen(port, () => {
console.log(`Server běží na http://localhost:${port}`);
});
V tomto příkladu:
- Funkce
isProductje vlastní typový strážce, který kontroluje, zda objekt odpovídá rozhraníProduct. - Uvnitř obslužné rutiny trasy
/productsse funkceisProductpoužívá k validaci těla požadavku. - Pokud je tělo požadavku platným produktem, TypeScript ví, že
req.bodyje typuProductv rámci blokuif.
Zohlednění globálních aspektů v návrhu API
Při navrhování API pro globální publikum je třeba zvážit několik faktorů, aby byla zajištěna přístupnost, použitelnost a kulturní citlivost.
- Lokalizace a internacionalizace (i18n a L10n):
- Dohoda o obsahu (Content Negotiation): Podporujte více jazyků a regionů prostřednictvím dohody o obsahu na základě hlavičky
Accept-Language. - Formátování data a času: Používejte formát ISO 8601 pro reprezentaci data a času, abyste se vyhnuli nejednoznačnosti napříč různými regiony.
- Formátování čísel: Zpracovávejte formátování čísel podle lokality uživatele (např. oddělovače desetinných míst a oddělovače tisíců).
- Zpracování měn: Podporujte více měn a v případě potřeby poskytněte informace o směnných kurzech.
- Směr textu: Přizpůsobte se jazykům zprava doleva (RTL), jako je arabština a hebrejština.
- Dohoda o obsahu (Content Negotiation): Podporujte více jazyků a regionů prostřednictvím dohody o obsahu na základě hlavičky
- Časová pásma:
- Ukládejte data a časy v UTC (Coordinated Universal Time) na straně serveru.
- Umožněte uživatelům specifikovat jejich preferované časové pásmo a odpovídajícím způsobem převádějte data a časy na straně klienta.
- Používejte knihovny jako
moment-timezonepro zpracování konverzí časových pásem.
- Kódování znaků:
- Používejte kódování UTF-8 pro všechna textová data k podpoře široké škály znaků z různých jazyků.
- Zajistěte, aby vaše databáze a další systémy pro ukládání dat byly nakonfigurovány pro použití UTF-8.
- Přístupnost:
- Dodržujte pokyny pro přístupnost (např. WCAG), aby vaše API bylo přístupné uživatelům s postižením.
- Poskytujte jasné a popisné chybové zprávy, které jsou snadno srozumitelné.
- Používejte sémantické prvky HTML a atributy ARIA ve své dokumentaci API.
- Kulturní citlivost:
- Vyhněte se používání kulturně specifických odkazů, idiomů nebo humoru, které nemusí být všemi uživateli pochopeny.
- Buďte ohleduplní k kulturním rozdílům ve stylech komunikace a preferencích.
- Zvažte potenciální dopad vašeho API na různé kulturní skupiny a vyhněte se udržování stereotypů nebo předsudků.
- Soukromí dat a zabezpečení:
- Dodržujte předpisy o ochraně osobních údajů, jako jsou GDPR (Obecné nařízení o ochraně osobních údajů) a CCPA (California Consumer Privacy Act).
- Implementujte silné mechanismy autentizace a autorizace pro ochranu uživatelských dat.
- Šifrujte citlivá data jak při přenosu, tak v klidu.
- Poskytněte uživatelům kontrolu nad jejich daty a umožněte jim k nim přistupovat, upravovat je a mazat je.
- Dokumentace API:
- Poskytněte komplexní a dobře organizovanou dokumentaci API, která je snadno srozumitelná a navigovatelná.
- Použijte nástroje jako Swagger/OpenAPI k generování interaktivní dokumentace API.
- Zahrňte příklady kódu ve více programovacích jazycích, abyste oslovili různorodé publikum.
- Přeložte svou dokumentaci API do více jazyků, abyste oslovili širší publikum.
- Zpracování chyb:
- Poskytujte specifické a informativní chybové zprávy. Vyhněte se obecným chybovým zprávám jako "Něco se pokazilo."
- Používejte standardní stavové kódy HTTP k indikaci typu chyby (např. 400 pro špatný požadavek, 401 pro neoprávněný, 500 pro vnitřní chybu serveru).
- Zahrňte chybové kódy nebo identifikátory, které lze použít ke sledování a ladění problémů.
- Logujte chyby na straně serveru pro ladění a monitorování.
- Omezení rychlosti (Rate Limiting): Implementujte omezení rychlosti k ochraně vašeho API před zneužitím a zajištění spravedlivého použití.
- Verzování: Použijte verzování API k umožnění zpětně kompatibilních změn a zabránění narušení stávajících klientů.
Závěr
Integrace TypeScriptu s Expressem významně zlepšuje spolehlivost a udržovatelnost vašich backend API. Využitím typové bezpečnosti v obslužných rutinách tras a middleware můžete zachytit chyby brzy v procesu vývoje a vytvářet robustnější a škálovatelnější aplikace pro globální publikum. Definováním typů požadavků a odpovědí zajistíte, že vaše API dodržuje konzistentní datovou strukturu, čímž se snižuje pravděpodobnost chyb za běhu. Nezapomeňte dodržovat osvědčené postupy, jako je povolení striktního režimu, používání rozhraní a aliasů typů a psaní jednotkových testů, abyste maximalizovali výhody TypeScriptu. Vždy zvažte globální faktory, jako je lokalizace, časová pásma a kulturní citlivost, abyste zajistili, že vaše API jsou přístupná a použitelná po celém světě.